package crmdna.common;

import static crmdna.common.AssertUtils.ensureNotNull;

import java.util.ArrayList;
import java.util.List;

import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.PropertyProjection;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.googlecode.objectify.Key;

public class ProjectionQuery<T1, T2> {
	
	private Class<T1> type;
	private Class<T2> resultType;
	
	private Iterable<Key<T1>> keys;
	
	private String property;
	
	public static <T3, T4> ProjectionQuery<T3, T4> pq(Class<T3> type, Class<T4> resultType) {
		ProjectionQuery<T3, T4> projectionQuery = new ProjectionQuery<>();
		projectionQuery.type = type;
		projectionQuery.resultType = resultType;
		
		return projectionQuery;
	}
	
	public ProjectionQuery<T1, T2> keys(Iterable<Key<T1>> keys) {
		this.keys = keys;
		return this;
	}
	
	public ProjectionQuery<T1, T2> property(String property) {
		this.property = property;
		return this;
	}
					
	@SuppressWarnings("unchecked")
	public List<T2> execute() {
		
		ensureNotNull(type, "kind cannot be null");
		ensureNotNull(resultType, "result type cannot be null");
		ensureNotNull(property, "result type cannot be null");
		ensureNotNull(keys, "keys cannot be null");
		
		List<com.google.appengine.api.datastore.Key> rawKeys = new ArrayList<>();
		for (Key<?> key : keys) {
			rawKeys.add(key.getRaw());
		}
		
		AsyncDatastoreService datastore = DatastoreServiceFactory
				.getAsyncDatastoreService();
		
		com.google.appengine.api.datastore.Query.Filter filter = new com.google.appengine.api.datastore.Query.FilterPredicate(
				Entity.KEY_RESERVED_PROPERTY, FilterOperator.IN, rawKeys);
		
		com.google.appengine.api.datastore.Query q = new com.google.appengine.api.datastore.Query(
				type.getSimpleName());

		q.setFilter(filter).addProjection(
				new PropertyProjection(property, resultType));

		List<Entity> entities = datastore.prepare(q).asList(
				FetchOptions.Builder.withLimit(20000));

		List<T2> list = new ArrayList<>();
		for (Entity entity : entities) {
			list.add((T2) entity.getProperty(property));
		}

		return list;
	}		
}
